improvement(tables): show error message tooltip on errored workflow and enrichment cells#5449
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
PR SummaryLow Risk Overview
Reviewed by Cursor Bugbot for commit 4785802. Bugbot is set up for automated code reviews on this repo. Configure here. |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit e8447c8. Configure here.
| // reactable assistant turn: no copy/thumbs row beneath the card, whether | ||
| // the card is awaiting answers or collapsed to its recap. | ||
| const endsWithQuestion = trimmedContent.endsWith('</question>') | ||
| const showActions = phase === 'settled' && !endsWithQuestion && (message.content || hasAnyBlocks) |
There was a problem hiding this comment.
Question suffix hides message actions
Medium Severity
showActions is suppressed whenever assistant content ends with </question>, but that suffix can remain after the question card is dismissed or after an invalid tag is dropped at parse time. Copy and feedback controls then stay hidden even though no interactive question UI is shown.
Reviewed by Cursor Bugbot for commit e8447c8. Configure here.
Greptile SummaryThis PR delivers two improvements: (1) error tooltip display for table cells, surfacing
Confidence Score: 4/5Safe to merge; the tooltip change in cell-render.tsx is minimal and correct, and the question card feature is well-tested. Two non-blocking concerns around prompt-newline parsing and the virtual list slot for hidden rows are worth a follow-up. The cell-render change is small and correct — error messages are properly threaded through the type and the null check correctly handles the nullable exec.error field. The question card addition is larger and well-covered by new tests, but the parseQuestionAnswerMessage parser assumes prompts never contain newlines (nothing rejects them), which would silently break transcript pairing. The virtual list hidden-row rendering leaves an unsized slot that resolves via ResizeObserver, potentially producing a brief layout flash on history load. question.tsx (prompt-newline edge case) and mothership-chat.tsx (virtual list hidden row sizing) Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[resolveCellRender] --> B{workflowGroupId?}
B -- yes --> C{blockError exists?}
C -- yes --> D[block-error with message]
C -- no --> E{exec.status?}
E -- error --> F[error with exec.error]
E -- other --> G[other kinds]
D --> H[CellRender switch]
F --> H
H --> I{kind.message truthy?}
I -- no --> J[StatusBadge plain]
I -- yes --> K[Tooltip wrapping StatusBadge]
L[messages loop] --> M{assistant has question tag?}
M -- yes --> N{next msg is user reply?}
N -- yes --> O[parseLastQuestionTag]
O --> P[parseQuestionAnswerMessage]
P -- matched --> Q[store answers, hide user bubble]
P -- no match --> R[show normally]
N -- no --> R
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
A[resolveCellRender] --> B{workflowGroupId?}
B -- yes --> C{blockError exists?}
C -- yes --> D[block-error with message]
C -- no --> E{exec.status?}
E -- error --> F[error with exec.error]
E -- other --> G[other kinds]
D --> H[CellRender switch]
F --> H
H --> I{kind.message truthy?}
I -- no --> J[StatusBadge plain]
I -- yes --> K[Tooltip wrapping StatusBadge]
L[messages loop] --> M{assistant has question tag?}
M -- yes --> N{next msg is user reply?}
N -- yes --> O[parseLastQuestionTag]
O --> P[parseQuestionAnswerMessage]
P -- matched --> Q[store answers, hide user bubble]
P -- no match --> R[show normally]
N -- no --> R
|
| const selections = [...selectedByStep] | ||
| selections[step] = [label] | ||
| setSelectedByStep(selections) | ||
| const customs = [...customByStep] | ||
| customs[step] = '' | ||
| setCustomByStep(customs) | ||
| setFreeText('') | ||
| finishStep(selections, customs) | ||
| } | ||
|
|
||
| const handleMultiToggle = (label: string) => { | ||
| const selections = [...selectedByStep] | ||
| const current = selections[step] ?? [] | ||
| selections[step] = current.includes(label) | ||
| ? current.filter((l) => l !== label) | ||
| : [...current, label] | ||
| setSelectedByStep(selections) | ||
| } | ||
|
|
||
| /** multi_select confirm: commits selections and/or typed text, then advances. */ | ||
| const submitMultiStep = () => { | ||
| finishStep(selectedByStep, commitCustom()) | ||
| } |
There was a problem hiding this comment.
Prompt newlines break round-trip parsing
formatQuestionAnswerMessage serialises each question as ${q.prompt} — ${answer} joined by \n, and parseQuestionAnswerMessage reconstitutes by splitting on \n and checking lines.length === questions.length. If an agent supplies a prompt that itself contains a newline (nothing in isQuestionItem rejects them), the serialised string has extra \n characters, lines.length exceeds questions.length, and parseQuestionAnswerMessage returns null. The paired user message is not hidden and the answered-card recap is never shown — the question card stays in its active state on reload, which is misleading.
A one-line guard in isQuestionItem (value.prompt.includes('\n')) is enough to reject such prompts at parse time and keep the contract safe.
| /** | ||
| * Parses a `<question>` tag body. Accepts a single question object or a | ||
| * non-empty array of them; single objects are normalized to a one-element | ||
| * array so the renderer only handles the array shape. | ||
| */ | ||
| /** | ||
| * Extracts the last complete `<question>` tag payload from raw message | ||
| * content. Used by the chat list to pair an assistant question card with the | ||
| * user message that answered it. | ||
| */ | ||
| export function parseLastQuestionTag(content: string): QuestionTagData | null { |
There was a problem hiding this comment.
The double JSDoc block here is a copy-paste artifact — the first
/**...*/ comment describes parseLastQuestionTag but was left hanging when parseQuestionTagBody was added immediately before it. Only one doc comment should precede each function.
| /** | |
| * Parses a `<question>` tag body. Accepts a single question object or a | |
| * non-empty array of them; single objects are normalized to a one-element | |
| * array so the renderer only handles the array shape. | |
| */ | |
| /** | |
| * Extracts the last complete `<question>` tag payload from raw message | |
| * content. Used by the chat list to pair an assistant question card with the | |
| * user message that answered it. | |
| */ | |
| export function parseLastQuestionTag(content: string): QuestionTagData | null { | |
| /** | |
| * Extracts the last complete `<question>` tag payload from raw message | |
| * content. Used by the chat list to pair an assistant question card with the | |
| * user message that answered it. | |
| */ | |
| export function parseLastQuestionTag(content: string): QuestionTagData | null { |
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
e8447c8 to
bda7be9
Compare
…nd enrichment cells
bda7be9 to
4785802
Compare


Summary
exec.blockErrors[blockId]); run-level errors surfaceexec.error(workflow not found, rate limit, enrichment billing errors, etc.)cell-render.tsx— the messages were already persisted and hydrated on the grid read path, the renderer was just dropping them. Falls back to the plain badge when no message existsType of Change
Testing
Tested manually; typecheck, lint, and check:api-validation:strict pass
Checklist